/*
 *  linux/fs/namei.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * Some corrections by tytso.
 */

#include <linux/sched.h> // 调试程序头文件,定义了任务结构task_struct,0的数据等.
#include <linux/kernel.h>
#include <asm/segment.h> // 段操作头文件.定义了有关段寄存器操作的嵌入式汇编函数.

#include <string.h>
#include <fcntl.h>    // 文件控制头文件.文件及其描述符的操作控制常数符号的定义.
#include <errno.h>    // 错误号头文件.包含系统中各种出错号.
#include <const.h>    // 常数符号头文件.目前仅定义i节点中i_mode字段的各标志位.
#include <sys/stat.h> // 文件状态头文件.含有文件或文件系统状态结构stat()和常量.

// 下面宏中右侧表达式是访问数组的一种特殊使用方法.它基于这样的一个事实,即用数组名和数组下标所表示的数组项(例如a[b])的值等同于使用数组首指针(地址)
// 加上该项偏移地址的形式的值*(a+b),同时可知项a[b]也可以表示成b[a]的形式.因此对于字符数组项形式为"LoveYou"[2](或者2["LoveYou"])就等同于*("LoveYou" + 2)
// 另外,字符串"LoveYou"在内存中被存储的位置就是其地址,因此数组项"LoveYou"[2]的值就是该字符串索引值为2的字符"v"所对应的ASCII码值0x76,或用八进制
// 表示就是0166.在C语言中,字符也可以用其ASCII码值来表示,方法是在字符的ASCII码值前面加一个反斜杠.例如字符"v"可以表示成"\x76"或者"\166".因此
// 对于不可显示的字符(例如ASCII码值为0x00--0x1f的控制字符)就可用其ASCII码值来表示.
//
// 下面是访问模式宏.x是头文件include/fcntl.h中行7行开始定义的文件访问(打开)标志.这个宏根据文件访问标志x的值来索引双引号中对应的数值.双引号中
// 有4个八进制数值(实际表示4个控制字符):"\004\002\006\377",分别表示读,写和执行的权限为:r,w,rw和wxrwxrwx,并且分别对应x的索引值0--3.例如,如果x为2,
// 则该宏返回八进制值006,表示可读可写(rw).另外,其中O_ACCMODE = 00003,是索引值x的屏蔽码.
#define ACC_MODE(x) ("\004\002\006\377"[(x)&O_ACCMODE])

/*
 *	permission()
 *
 * is used to check for read/write/execute permissions on a file.
 * I don't know if we should look at just the euid or both euid and
 * uid, but that should be easily changed.
 */
/*
 *	permission()
 *
 * 该函数用于检测一个文件的读/写/执行权限.我不知道是否只需检查euid,还是需要检查euid和uid两者,不过这很容易修改.
 */
// 检测文件访问许可权限.
// 参数:inode - 文件的i节点指针;mask - 访问属性屏蔽码.
// 返回:访问许可返回1,否则返回0.
int permission(struct inode *inode, int mask)
{
    int mode = inode->i_mode; // 文件访问属性.

    /* special case: not even root can read/write a deleted file */
    /* 特殊情况:即使是超级用户(root)也不能读/写一个已被删除的文件. */
    // 如果i节点有对应的设备,但该i节点的链接计数值等于0,表示该文件已被删除,则返回.
    if (inode->i_dev && !inode->i_nlink)
        return 0;
    // 如果进程的有效用户id(euid)与i节点的用户id相同,则取文件宿主的访问权限
    else if (current->euid == inode->i_uid)
        mode >>= 6;
    // 如果进程有效组id(egid)与i节点的组id相同,则取组用户的访问权限
    else if (in_group_p(inode->i_gid))
        mode >>= 3;
    // 最后判断如果所取的的访问权限与屏蔽码相同,或者是超级用户,则返回1,否则返回0.
    if (((mode & mask & 0007) == mask) || suser())
        return 1;
    return 0;
}

/*
 * ok, we cannot use strncmp, as the name is not in our data space.
 * Thus we'll have to use match. No big problem. Match also makes
 * some sanity tests.
 *
 * NOTE! unlike strncmp, match returns 1 for success, 0 for failure.
 */
/*
 * ok,我们不能使用strncmp字符串比较函数,因为名称不在我们的数据空间(不在内核空间).因而我们只能使用match().问题不大,match()同样也处理一些
 * 完整的测试.
 *
 * 注意!与strncmp不同的是match()成功时返回1,失败时返回0.
 */
// 指定长度字符串比较函数.
// 参数:len - 比较的字符串长度;name - 文件名指针;de - 目录项结构.
// 返回:相同返回1,不同返回0.
// static int match(int len, const char * name, struct minix_dir_entry * de)
// {
// 	register int same __asm__("ax");

// 	// 首先判断函数参数的有效性.如果目录项指针空,或者目录项i节点等于0,或者要比较的字符串长度超过文件名长度,则返回0(不匹配).
// 	if (!de || !de->inode || len > MINIX_NAME_LEN)
// 		return 0;
// 	/* "" means "." ---> so paths like "/usr/lib//libc.a" work */
//     /* ""当作"."来看待 ---> 这样就能处理象"/usr/lib//libc.a"那样的路径名 */
//     // 如果比较的长度len等于0并且目录项中文件名的第1个字符是'.',并且只有这么一个字符,那么我们就认为是相同的,因此返回1(匹配)
// 	if (!len && (de->name[0] == '.') && (de->name[1] == '\0'))
// 		return 1;
// 	// 如果要比较的长度len小于NAME_LEN,但是目录项中文件名长度超过len,则也返回0(不匹配)
// 	// 第75行上对目录项中文件名长度是否超过len的判断方法是检测name[len]是否为NULL.若长度超过len,则name[len]处就是一个不是NULL的普通字符.而对于长度
// 	// 为len的字符串name,字符name[len]就应该是NULL.
// 	if (len < MINIX_NAME_LEN&& de->name[len])
// 		return 0;
// 	// 然后使用嵌入汇编语句进行快速比较操作.它会在用户数据空间(fs段)执行字符串的比较操作.%0 - eax(比较结果same);%1 - eax(eax初值0);%2 - esi(名字指针);
// 	// %3 - edi(目录项名指针);%4 - ecs(比较的字节长度值len).
// 	__asm__(\
// 		"cld\n\t"							// 清方向标志位.
// 		"fs ; repe ; cmpsb\n\t"				// 用户空间执行循环比较[esi++]和[edi++]操作.
// 		"setz %%al"							// 若比较结果一样(zf=0)则置al=1(same=eax).
// 		:"=a" (same)
// 		:"0" (0), "S" ((long) name), "D" ((long) de->name), "c" (len)
// 		:);
// 	return same;							// 返回比较结果.
// }

/*
 *	find_entry()
 *
 * finds an entry in the specified directory with the wanted name. It
 * returns the cache buffer in which the entry was found, and the entry
 * itself (as a parameter - res_dir). It does NOT read the inode of the
 * entry - you'll have to do that yourself if you want to.
 *
 * This also takes care of the few special cases due to '..'-traversal
 * over a pseudo-root and a mount point.
 */
/*
 *	find_entry()
 *
 * 在指定目录中寻找一个与名字匹配的目录项.返回一个含有找到目录项的高速缓冲块以及目录项本身(作为一个参数--res_dir).该函数并不读取目录
 * 项的i节点--如果需要的话则自己操作.
 *
 * 由于有'..'目录项,因此在操作期间也会对几种特殊情况分别处理--比如横越一个伪根目录以及安装点.
 */
// 查找指定目录和文件名的目录项.
// 参数:*dir - 指定目录i节点的指针;name - 文件名;namelen - 文件名长度;该函数在指定目录的数据(文件)中搜索指定文件名的目录项.并对指定
// 文件名是'..'的情况根据当前进行的相关设置进行特殊处理.
// 返回:成功则返回高速缓冲区指针,并在*res_dir处返回的目录项结构指针.失败则返回空指针NULL.
// static struct buffer_head * find_entry(struct inode ** dir,
// 	const char * name, int namelen, struct minix_dir_entry ** res_dir)
// {
// 	int entries;
// 	int block,i;
// 	struct buffer_head * bh;
// 	struct minix_dir_entry * de;
// 	struct super_block * sb;

// 	// 同样,本函数一开始也需要对函数参数的有效性进行判断和验证.如果我们在前面第30行定义了符号常数NO_TRUNCATE,那么如果文件名长度超过最大长度NAME_LEN,
// 	// 则不予处理.如果没有定义过NO_TRUNCATE,那么在文件名长度超过最大长度NAME_LEN时截短之.
// #ifdef NO_TRUNCATE
// 	if (namelen > MINIX_NAME_LEN)
// 		return NULL;
// #else
// 	if (namelen > MINIX_NAME_LEN)
// 		namelen = MINIX_NAME_LEN;
// #endif
// 	// 首先计算本目录中目录项项数entries.目录i节点i_size字段中含有本目录包含的数据长度,因此其除以一个目录项的长度(16字节)即可得到该目录中目录项数.然后
// 	// 置空返回目录项结构指针.
// 	entries = (*dir)->i_size / (sizeof (struct minix_dir_entry));
// 	*res_dir = NULL;
// 	// 接下来我们对目录项文件名是'..'的情况进行特殊处理.如果当前进程指定的根i节点就是函数参数指定的目录,则说明对于本进程来说,这个目录就是它的伪根目录,即进程
// 	// 只能访问该目录中的项而不能退到其父目录中去.也即对于该进程本目录就如同是文件系统的根目录.因此我们需要将文件名修改为'.'.
// 	// 否则,如果该目录的i节点号等于ROOT_INO(1号)的话,说明确实是文件系统的根i节点.则取文件系统的超级块.如果被安装到的i节点存在,则先放回原i节点,然后对被
// 	// 安装到的i节点进行处理.于是我们让*dir指向该被安装到的i节点;并且该i节点的引用数加1.即针对这种情况,我们悄悄进行了"偷梁换柱"工程:)
// 	/* check for '..', as we might have to do some "magic" for it */
// 	/* 检查目录项'..',因为我们可能需要对其进行特殊处理 */
// 	if (namelen == 2 && get_fs_byte(name) == '.' && get_fs_byte(name + 1) == '.') {
// 		/* '..' in a pseudo-root results in a faked '.' (just change namelen) */
// 		/* 伪根中的'..'如同一个假'.'(只需改变名字长度) */
// 		if ((*dir) == current->root)
// 			namelen = 1;
// 		else if ((*dir)->i_ino == MINIX_ROOT_INO) {
// 			/* '..' over a mount-point results in 'dir' being exchanged for the mounted
// 			   directory-inode. NOTE! We set mounted, so that we can iput the new dir */
// 			/* 在一个安装点上的'..'将导致目录交换到被安装文件系统的目录i节点上.注意! 由于我们设置了mounted标志,因而我们能够放回该新目录 */
// 			sb = get_super((*dir)->i_dev);
// 			if (sb->s_mounted) {
// 				iput(*dir);
// 				(*dir)=sb->s_mounted;
// 				(*dir)->i_count++;
// 			}
// 		}
// 	}
// 	// 现在我们开始正常操作，查找指定文件名的目录项在什么地方。因此我们需要读取目录的数据，即取出目录i节点对应块设备数据区中的数据块（逻辑块）信息。这些逻辑块的
// 	// 块号保存在i节点结构的i_zone[9]数组中.我们先取其中第1个块号.如果目录i节点指向的第一个直接盘块号为0,则说明该目录竟然不含数据,这不正常.于是返回NULL退出.
// 	// 否则我们就从节点所在设备读取指定的目录项数据块.当然,如果不成功,则也返回NULL退出.
// 	if (!(block = (*dir)->i_data[0]))
// 		return NULL;
// 	if (!(bh = bread((*dir)->i_dev, block)))
// 		return NULL;
// 	// 此时我们就在这个读取的目录i节点数据块中搜索匹配指定文件名的目录项.首先让de指向缓冲块中的数据块部分,并在不超过目录项数据的条件下,循环执行搜索.其中i是目录
// 	// 中的目录项索引号,在循环开始时初始化为0.
// 	i = 0;
// 	de = (struct minix_dir_entry *) bh->b_data;
// 	while (i < entries) {
// 		// 如果当前目录项数据块已经搜索完,还没有找到匹配的目录项,则释放当前目录项数据块.再读入目录的下一个逻辑块.若这块为空,则只要还没有搜索完目录中的所有目录项,就
// 		// 跳过该块,继续读目录的下一逻辑块.若该块不空,就让de指向该数据块,然后在其中继续搜索.其中141行上i/DIR_ENTRIES_PER_BLOCK可得到当前搜索的目录项所在目录文件中的
// 		// 块号,而bmap()函数(inode.c)则可计算出在设备上对应的逻辑块号.
// 		if ((char *)de >= BLOCK_SIZE + bh->b_data) {
// 			brelse(bh);
// 			bh = NULL;
// 			if (!(block = minix_bmap(*dir, i / MINIX_DIR_ENTRIES_PER_BLOCK)) ||
// 			    !(bh = bread((*dir)->i_dev, block))) {
// 				i += MINIX_DIR_ENTRIES_PER_BLOCK;
// 				continue;
// 			}
// 			de = (struct minix_dir_entry *) bh->b_data;
// 		}
// 		// 如果找到匹配的目录项的话,则返回目录项结构指针de和该目录项i节点指针*dir以及该目录项数据块指针bh,并退出函数.否则继续在目录项数据块中比较下一个目录项.
// 		if (match(namelen, name, de)) {
// 			*res_dir = de;
// 			return bh;
// 		}
// 		de++;
// 		i++;
// 	}
// 	// 如果指定目录中的所有目录项都搜索赛后,还没有找到相应的目录项,则释放目录的数据块,最后返回NULL(失败).
// 	brelse(bh);
// 	return NULL;
// }

/*
 *	add_entry()
 *
 * adds a file entry to the specified directory, using the same
 * semantics as find_entry(). It returns NULL if it failed.
 *
 * NOTE!! The inode part of 'de' is left at 0 - which means you
 * may not sleep between calling this and putting something into
 * the entry, as someone else might have used it while you slept.
 */
/*
 *      add_entry()
 * 使用与find_entry()同样的方法，往指定目录中添加一指定文件名的目录项。如果失败则返回NULL。
 *
 * 注意！！'de'（指定目录项结构指针）的i节点部分被设置为0 - 这表示在调用该函数和往目录项中添加信息之间不能去睡眠，
 * 因为如果睡眠，那么其他人（进程）可能会使用该目录项。
 */
// 根据指定的目录和文件名添加目录项。
// 参数：dir - 指定目录的i节点；name - 文件名；namelen - 文件名长度；
// 返回：高速缓冲区指针；res_dir - 返回的目录项结构指针。
// static struct buffer_head * add_entry(struct inode * dir,
// 	const char * name, int namelen, struct minix_dir_entry ** res_dir)
// {
// 	int block, i;
// 	struct buffer_head * bh;
// 	struct minix_dir_entry * de;

// 	// 同样，本函数一开始也需要对函数参数的有效性进行判断和验证。如果我们在前面定义了符号常数NO_TRUNCATE，那么如果文件
// 	// 名长度超过最大长度NAME_LEN，则不予处理。如果没有定义过NO_TRUNCATE，那么在文件长度超过最大长度NAME_LEN时截短之。
// 	*res_dir = NULL;                							// 用于返回目录项结构指针。
// #ifdef NO_TRUNCATE
// 	if (namelen > MINIX_NAME_LEN)
// 		return NULL;
// #else
// 	if (namelen > MINIX_NAME_LEN)
// 		namelen = MINIX_NAME_LEN;
// #endif
// 	// 现在我们开始操作，向指定目录中添加一个指定文件名的目录项。因此我们需要先读取目录的数据，即取出目录i节点对应块设备
// 	// 数据区中的数据块（逻辑块）信息。这些逻辑块的块号保存在i节点结构的i_zone[9]数组中。我们先取其第1个块号。如果目录
// 	// i节点指向的第一个直接磁盘块号为0,则说明该目录竟然不含数据，这不正常。于是返回NULL退出。否则我们就从节点所在设备读取
// 	// 指定的目录项数据块。如果不成功，则也返回NULL退出。另外，如果参数提供的文件名长度等于0,则也返回NULL退出。
// 	if (!namelen)
// 		return NULL;
// 	if (!(block = dir->i_data[0]))
// 		return NULL;
// 	if (!(bh = bread(dir->i_dev, block)))
// 		return NULL;
// 	// 此时我们就在这个目录i节点数据块中循环查找最后未使用的空目录项。首先让目录项结构指针de指向缓冲块中的数据块部分，即第
// 	// 一个目录项处。其中i是目录中的目录项索引号，在循环开始时初始化为0。
// 	i = 0;
// 	de = (struct minix_dir_entry *) bh->b_data;
// 	while (1) {
// 		// 如果当前目录项数据块已经搜索完毕，但还没有找到需要的空目录项，则释放当前目录项数据块，再读入目录的下一个逻辑块。如果
// 		// 对应的逻辑块不存在就创建一块。若读取或创建操作失败则返回空。如果此次读取的磁盘逻辑块数据返回的缓冲块指针为空，说明这
// 		// 块逻辑块可能是因为不存在而新创建的空块，则把目录项索引值加上一块逻辑块所能容纳的目录项数DIR_ENTRIES_PER_BLOCK，
// 		// 用以跳过该块并继续搜索。否则说明新读入的块上有目录项数据，于是让目录项结构指针de指向该块的缓冲块数据部分，然后在其中
// 		// 继续搜索。其中i/DIR_ENTRIES_PER_BLOCK可计算得到当前搜索的目录项i所在目录文件中的块号，而create_block()函数
// 		// （inode.c）则可读取或创建出在设备上对应的逻辑块。
// 		if ((char *)de >= BLOCK_SIZE + bh->b_data) {
// 			brelse(bh);
// 			bh = NULL;
// 			block = minix_create_block(dir, i / MINIX_DIR_ENTRIES_PER_BLOCK);
// 			if (!block)
// 				return NULL;
// 			if (!(bh = bread(dir->i_dev, block))) {          			// 若空则跳过该块继续。
// 				i += MINIX_DIR_ENTRIES_PER_BLOCK;
// 				continue;
// 			}
// 			de = (struct minix_dir_entry *) bh->b_data;
// 		}
// 		// 如果当前所操作的目录项序号i乘上结构大小所得长度值已经超过目录i节点信息所指出的目录数据长度值i_size，则说明整个目录
// 		// 文件数据中没有由于删除文件留下的空目录项，因此我们只能把需要添加的新目录项附加到目录文件数据的末端处。于是对该处目录
// 		// 项进行设置（置该目录项的i节点指针为空），并更新该目录文件的长度值（加上一个目录项的长度），然后设置目录的i节点已修改
// 		// 标志，再更新该目录的改变时间为当前时间。
// 		if (i * sizeof(struct minix_dir_entry) >= dir->i_size) {
// 			de->inode = 0;
// 			dir->i_size = (i + 1) * sizeof(struct minix_dir_entry);
// 			dir->i_dirt = 1;
// 			dir->i_ctime = CURRENT_TIME;
// 		}
// 		// 若当前搜索的目录项de的i节点为空，则表示找到一个还未使用的空闲目录项或是添加的新目录项。于是更新目录的修改时间为当前
// 		// 时间，并从用户数据区复制文件名到该目录项的文件名字段，置含有本目录项的相应高速缓冲块已修改标志。返回该目录项的指针以及
// 		// 该高速缓冲块的指针，退出。
// 		if (!de->inode) {
// 			dir->i_mtime = CURRENT_TIME;
// 			for (i = 0; i < MINIX_NAME_LEN; i++)
// 				de->name[i] = (i < namelen) ? get_fs_byte(name + i) : 0;
// 			bh->b_dirt = 1;
// 			*res_dir = de;
// 			return bh;
// 		}
// 		de++;           												// 如果该目录项已经被使用，则继续检测下一个目录项。
// 		i++;
// 	}
// 	// 本函数执行不到这里。这也许是Linus在写这段代码时，先复制了上面find_entry()函数的代码，而后修改成本函数的。
// 	brelse(bh);
// 	return NULL;
// }

int lookup(struct inode *dir, const char *name, int len, struct inode **result)
{
    struct super_block *sb;

    *result = NULL;
    if (len == 2 && get_fs_byte(name) == '.' && get_fs_byte(name + 1) == '.')
    {
        if (dir == current->root)
            len = 1;
        else if ((sb = dir->i_sb) && (dir == sb->s_mounted))
        {
            sb = dir->i_sb;
            iput(dir);
            if (dir = sb->s_covered)
                dir->i_count++;
        }
    }
    if (!dir)
        return -ENOENT;
    if (!permission(dir, MAY_EXEC))
    {
        iput(dir);
        return -EACCES;
    }
    if (!len)
    {
        *result = dir;
        return 0;
    }
    if (!dir->i_op || !dir->i_op->lookup)
    {
        iput(dir);
        return -ENOENT;
    }
    return dir->i_op->lookup(dir, name, len, result);
}

// 查找符号链接的i节点.
// 参数:dir - 目录i节点;inode - 目录项i节点.
// 返回:返回符号链接到文件的i节点指针.出错返回NULL.
static struct inode *follow_link(struct inode *dir, struct inode *inode)
{
    // 首先判断函数参数的有效性.如果没有给出目录i节点,我们就使用进程任务结构中设置的根i节点,并把链接数增1.如果没有给出目录
    // 项i节点,则放回目录i节点后返回NULL.如果指定目录项不是一个符号链接,就直接返回目录项对应的i节点inode.
    if (!dir || !inode)
    {
        iput(dir);
        iput(inode);
        return NULL;
    }
    if (!inode->i_op || !inode->i_op->follow_link) {
        iput(dir);
        return inode;
    }
    return inode->i_op->follow_link(dir, inode);
}

/*
 *	get_dir()
 *
 * Getdir traverses the pathname until it hits the topmost directory.
 * It returns NULL on failure.
 */
/*
 *	get_dir()
 *
 * 该函数根据给出的路径名进行搜索,直到达到最顶端的目录.如果失败是返回NULL.
 */
// 从指定目录开始搜寻指定路径名的目录(或文件名)的i节点.
// 参数:pathname - 路径名;inode - 指定起始目录的i节点.
// 返回:目录或文件的i节点指针.失败时返回NULL.
// static struct inode *get_dir(const char *pathname, struct inode *inode)
// {
//     char c;
//     const char *thisname;
//     struct buffer_head *bh;
//     int namelen, inr;
//     struct minix_dir_entry *de;
//     struct inode *dir;

//     // 首先判断参数有效性.如果给出的指定目录的i节点指针inode为空,则使用当前进程的工作目录i节点.
//     if (!inode)
//     {
//         inode = current->pwd; // 进程的当前工作目录i节点.
//         inode->i_count++;
//     }
//     // 如果用户指定路径名的第1个字符是'/',则说明路径名是绝对路径名.则应该从当前进程任务结构中设置的根(或伪根)i节点开始操作.
//     // 于是我们需要先放回参数指定的或者设定的目录i节点,并取得进程使用的根i节点.然后把该i节点的引用计数加1,
//     // 并删除路径名的第1个字符'/'.这样就可以保证进程只能以其设定的根i节点作为搜索的起点.
//     if ((c = get_fs_byte(pathname)) == '/')
//     {
//         iput(inode);           // 放回原i节点.
//         inode = current->root; // 为进程指定的根i节点.
//         pathname++;
//         inode->i_count++;
//     }
//     // 然后针对路径名中的各个目录名部分和文件名进行循环处理。在循环处理过程中，我们先要对当前正在处理的目录名部分的i节点进行有效性判断，并且把
//     // 变量thisname指向当前正在处理的目录名部分。如果该i节点表明当前处理的目录名部分不是目录类型，或者没有可进入该目录的访问许可，则放回该i节点
//     // 并返回NULL退出。当然在刚进入循环时，当前目录的ｉ节点inode就是进程根i节点或者是当前工作目录的i节点，或者是参数指定的某个搜索起始目录的i节点。
//     while (1)
//     {
//         thisname = pathname;
//         if (!S_ISDIR(inode->i_mode) || !permission(inode, MAY_EXEC))
//         {
//             iput(inode);
//             return NULL;
//         }
//         // 每次循环我们处理路径名中一个目录名(或文件名)部分.因此在每次循环中我们都要从路径名字符串中分离出一个目录名(或文件名).方法是从当前路径名指针
//         // pathname开始处搜索检测字符,直到字符是一个结尾符(NULL)或者是一个'/'字符.此时变量namelen正好是当前处理目录名部分的长度,而变量thisname正指向
//         // 该目录名部分的开始处.此时如果字符是结尾符NULL,则表明已经搜索到路径名末尾,并已到达最后指定目录名或文件名,则返回该i节点指针退出.
//         // 注意!如果路径名中最后一个名称也是一个目录名,但其后面没有加上'/'字符,则函数不会返回该最后目录名的i节点!例如:对于路径/usr/src/linux,该函数将
//         // 只返回src/目录名的i节点.
//         for (namelen = 0; (c = get_fs_byte(pathname++)) && (c != '/'); namelen++)
//             /* nothing */;
//         if (!c)
//             return inode;
//         // 在得到当前目录名部分(或文件名)后,我们调用查找目录项函数find_entry()在当前处理的目录中寻找指定名称的目录项.如果没有找到,则放回该i节点,并返回
//         // NULL退出.然后在找到的目录项中取出其i节点号inr和设备号idev,释放包含该目录项的高速缓冲块并放回该i节点.然后取节点号inr的i节点inode,并以该目录
//         // 项为当前目录继续循环处理路径名中的下一目录名部分(或文件名).如果当前处理的目录项是一个符号链接名,则使用follow_link()就可以得到其指向的目录项名i节点.
//         if (!(bh = find_entry(&inode, thisname, namelen, &de)))
//         {
//             iput(inode);
//             return NULL;
//         }
//         inr = de->inode; // 当前目录名部分的i节点号.
//         brelse(bh);
//         dir = inode;
//         if (!(inode = iget(dir->i_dev, inr)))
//         { // 取i节点内容.
//             iput(dir);
//             return NULL;
//         }
//         if (!(inode = follow_link(dir, inode)))
//             return NULL;
//     }
// }

/*
 *	dir_namei()
 *
 * dir_namei() returns the inode of the directory of the
 * specified name, and the name within that directory.
 */
/*
 *	dir_namei()
 *
 * dir_namei()函数返回指定目录名的i节点指针,以及在最顶层目录的名称.
 */
// 参数:pathname - 目录路径名;namelen - 路径名长度;name - 返回的最顶层目录名.
// base - 搜索起始目录的i节点.
// 返回:指定目录名最顶层的i节点指针和最顶层目录名称及长度.出错时返回NULL.
// 注意!!这里"最顶层目录"是指路径名中最靠近末端的目录.
static struct inode *dir_namei(const char *pathname,
                               int *namelen, const char **name, struct inode *base)
{
    char c;
    const char *thisname;
    int len, error;
    struct inode *inode;

    if (!base) {
        base = current->pwd;
        base->i_count++;
    }
    if ((c = get_fs_byte(pathname)) == '/') {
        iput(base);
        base = current->root;
        pathname++;
        base->i_count++;
    }
    while (1)
    {
        thisname = pathname;
        for (len=0; (c=get_fs_byte(pathname++)) &&(c!='/'); len++)
            ;
        if (!c) break;
        base->i_count++;
        error = lookup(base, thisname, len, &inode);
        if (error) {
            iput(base);
            return NULL;
        }
        if (!(base = follow_link(base, inode)))
            return NULL;
    }
    *name = thisname;
    *namelen = len;
    return base;
}

// 取指定路径名的i节点内部函数.
// 参数:pathname - 路径名;base - 搜索起点目录i节点;follow_links - 是否跟随符号链接的标志,1 - 需要,0 不需要.
struct inode *_namei(const char *pathname, struct inode *base,
                     int follow_links)
{
    const char *basename;
    int namelen, error;
    struct inode *inode;

    // 首先查找指定路径名中最顶层目录的目录名并得到其i节点.若不存在,则返回NULL退出.如果返回的最顶层名字的长度是0,则表示该路径名以一个目录名为
    // 最后一项.因此说明我们已经找到对应目录的i节点,可以直接返回该i节点退出.
    if (!(base = dir_namei(pathname, &namelen, &basename, base)))
        return NULL;
    base->i_count++;
    error = lookup(base, basename, namelen, &inode);
    if (error) {
        iput(base);
        return NULL;
    }
    if (follow_links)
        inode = follow_link(base, inode);
    else
        iput(base);
    if (inode) {
        inode->i_atime = CURRENT_TIME;
        inode->i_dirt = 1;
    }
    return inode;
}

// 取指定路径名的i节点，不跟随符号链接。
// 参数：pathname - 路径名。
// 返回：对应的i节点。
struct inode *lnamei(const char *pathname)
{
    return _namei(pathname, NULL, 0);
}

/*
 *	namei()
 *
 * is used by most simple commands to get the inode of a specified name.
 * Open, link etc use their own routines, but this is enough for things
 * like 'chmod' etc.
 */
/*
 *	namei()
 *
 * 该函数被许多简单命令用于取得指定路径名称的i节点.open,link等则使用它们自己的相应函数.但对于像修改模式"chmod"等这样的命令,该
 * 函数已足够用了.
 */
// 取指定路径名的i节点,跟随符号链接.
// 参数:pathname - 路径名.
// 返回:对应的i节点.
struct inode *namei(const char *pathname)
{
    return _namei(pathname, NULL, 1);
}

/*
 *	open_namei()
 *
 * namei for open - this is in fact almost the whole open-routine.
 */
/*
 * 	open_namei()
 *
 * open()函数使用的namei函数 - 这其实几乎是完整的打开文件程序.
 */
// 文件打开namei函数.
// 参数filename是文件名,flag是打开文件标志,它可取值:O_RDONLY(只读),O_WRONLY(只写)或O_RDWR(读写),以及O_CREAT(创建),
// O_EXCL(被创建文件必须不存在),O_APPEND(在文件尾添加数据)等其他一些标志的组合,如果本调用创建了一个新文件,则mode就用于指定
// 文件的许可属性.这些属性有S_IRWXU(文件宿主具有读,写和执行权限),S_IRUSR(用户具有读文件权限),S_IRWXG(组成员有读,写
// 执行)等等.对于新创建的文件,这些属性只应用于将来对文件的访问,创建了只读文件的打开调用也将返回一个读写的文件句柄.如果调用
// 操作成功,则返回文件句柄(文件描述符),否则返回出错码.参见sys/stat.h,fcntl.h.
// 返回:成功返回0,否则返回出错码;res_inode - 返回对应文件路径名的i节点指针.
int open_namei(const char *pathname, int flag, int mode,
               struct inode **res_inode)
{
    const char *basename;
    int namelen, error;
    struct inode *dir, *inode;

    // 首先对函数参数进行合理的处理.如果文件访问模式标志是只读(O),但是文件截零标志O_TRUNC却置位了,则在文件打开标志中添加只写标志
    // O_WRONLY.这样做的原因是由于截零标志O_TRUNC必须在文件可写情况下有效.
    if ((flag & O_TRUNC) && !(flag & O_ACCMODE))
        flag |= O_WRONLY;
    // 使用当前进程的文件访问许可屏蔽码,屏蔽掉给定模式中的相应位,并添上普通文件标志I_REGULAR.
    // 该标志将用于打开的文件不存在而需要创建文件时,作为新文件的默认属性
    mode &= 0777 & ~current->umask;
    mode |= I_REGULAR; // 常规文件标志.见参见include/const.h文件.
    // 然后根据指定的路径名寻找到对应的i节点,以及最顶端目录名及其长度.此时如果最顶端目录名长度为0(例如'/usr/'这种路径名的情况),那么
    // 若操作不是读写,创建和文件长度截0,则表示是在打开一个目录名文件操作.于是直接返回该目录的i节点并返回0退出.否则说明进程操作非法,于是
    // 放回该i节点,返回出错码.
    if (!(dir = dir_namei(pathname, &namelen, &basename, NULL)))
        return -ENOENT;
    // 文件名字为空，则返回
    if (!namelen)
    { /* special case: '/usr/' etc */
        if (!(flag & (O_ACCMODE | O_CREAT | O_TRUNC)))
        {
            *res_inode = dir;
            return 0;
        }
        iput(dir);
        return -EISDIR;
    }
    dir->i_count++;
    error = lookup(dir, basename, namelen, &inode);
    if (error) {
        if (!(flag & O_CREAT)) {
            iput(dir);
            return error;
        }
        if (!permission(dir, MAY_WRITE)) {
            iput(dir);
            return -EACCES;
        }
        if (!dir->i_op || !dir->i_op->create) {
            iput(dir);
            return -EACCES;
        }
        return dir->i_op->create(dir, basename, namelen, mode, res_inode);
    }
    if (flag & O_EXCL) {
        iput(dir);
        iput(inode);
        return -EEXIST;
    }
    if (!(inode = follow_link(dir, inode)))
        return -ELOOP;
    if ((S_ISDIR(inode->i_mode) && (flag & O_ACCMODE)) || 
        !permission(inode, ACC_MODE(flag))) {
        iput(inode);
        return -EPERM;
    }
    inode->i_atime = CURRENT_TIME;
    if (flag & O_TRUNC)
        if (inode->i_op && inode->i_op->truncate)
            inode->i_op->truncate(inode);
    *res_inode = inode;
    return 0;
}

// 创建一个设备特殊文件或普通文件节点（node）。
// 该函数创建名称为filename，由mode和dev指定的文件系统节点（普通文件、设备特殊文件或命名管道）。
// 参数：filename - 路径名；mode - 指定使用许可以及所创建节点的类型；dev - 设备号。
int sys_mknod(const char *filename, int mode, int dev)
{
    const char *basename;
    int namelen;
    struct inode *dir;

    // 首先检查操作许可和参数的有效性并取路径名中顶层目录的i节点。如果不是超级用户，则返回访问许可出错码。
    // 如果不是超级用户，则返回访问许可出错码。
    if (!suser())
        return -EPERM;
    // 如果找不到对应路径名中顶层目录的i节点，则返回出错码。
    if (!(dir = dir_namei(filename, &namelen, &basename, NULL)))
        return -ENOENT;
    // 如果最顶端的文件名长度为0，则说明给出的路径名最后没有指定文件名，放回该目录i节点，返回出错码退出。
    if (!namelen)
    {
        iput(dir);
        return -EPERM;
    }
    if (!permission(dir, MAY_WRITE)) {
        iput(dir);
        return -EACCES;
    }
    if (!dir->i_op || !dir->i_op->mknod) {
        iput(dir);
        return -EPERM;
    }
    return dir->i_op->mknod(dir, basename, namelen, mode, dev);
}

// 创建一个目录。
// 参数：pathname - 路径名；mode - 目录使用的权限属性。
// 返回：成功则返回0,否则返回出错码。
int sys_mkdir(const char *pathname, int mode)
{
    const char *basename;
    int namelen;
    struct inode *dir;

    // 首先检查参数的有效性并取路径名中顶层目录的i节点。如果找不到对应路径名中顶层目录的i节点，则返回出错码。
    if (!(dir = dir_namei(pathname, &namelen, &basename, NULL)))
        return -ENOENT;
    // 如果最顶端文件名长度为0,则说明给出的路径名最后没有指定文件名，放回该目录i节点，返回出错码退出。
    if (!namelen)
    {
        iput(dir);
        return -ENOENT;
    }
    // 如果在该目录中没有写的权限，则放回该目录i节点，返回访问许可出错码退出。
    // 如果不是超级用户，则返回访问许可出错码。
    if (!permission(dir, MAY_WRITE))
    {
        iput(dir);
        return -EPERM;
    }
    if (!dir->i_op || !dir->i_op->mkdir) {
        iput(dir);
        return -EACCES;
    }
    return dir->i_op->mkdir(dir, basename, namelen, mode);
}

// 删除目录。
// 参数：name - 目录名（路径名）。
// 返回：返回0表示成功，否则返回出错号。
int sys_rmdir(const char *name)
{
    const char *basename;
    int namelen;
    struct inode *dir;

    // 首先检查参数的有效性并取路径名中顶层目录的i节点。如果找不到对应路径名中顶层目录的i节点，则返回出错码。如果最顶端
    // 文件名长度为0,则说明给出的路径名最后没有指定文件名，放回该目录i节点，返回出错码退出。如果在该目录中没有写的权限，
    // 则放回该目录i节点，返回访问许可出错码退出。如果不是超级用户，则返回访问许可出错码。
    if (!(dir = dir_namei(name, &namelen, &basename, NULL)))
        return -ENOENT;
    if (!namelen)
    {
        iput(dir);
        return -ENOENT;
    }
    if (!permission(dir, MAY_WRITE))
    {
        iput(dir);
        return -EPERM;
    }
    if (!dir->i_op || !dir->i_op->rmdir) {
		iput(dir);
		return -EPERM;
	}
	return dir->i_op->rmdir(dir,basename,namelen);
}

// 删除（释放）文件名对应的目录项。
// 从文件系统删除一个名字。如果是文件的最后一个链接，并且没有进程正打开该文件，则该文件也将被删除，并释放所占用的设备空间。
// 参数：name - 文件名（路径名）。
// 返回：成功则返回0,否则返回出错号。
int sys_unlink(const char *name)
{
    const char *basename;
    int namelen;
    struct inode *dir;

    // 首先检查参数的有效性并取路径名中顶层目录的i节点。如果找不到对应路径名中顶层目录的i节点，则返回出错码。如果最顶端
    // 文件名长度为0,则说明给出的路径名最后没有指定文件名，放回该目录i节点，返回出错码退出。如果在该目录中没有写的权限，
    // 则放回该目录i节点，返回访问许可出错码退出。如果不是超级用户，则返回访问许可出错码。
    if (!(dir = dir_namei(name, &namelen, &basename, NULL)))
        return -ENOENT;
    if (!namelen)
    {
        iput(dir);
        return -ENOENT;
    }
    if (!permission(dir, MAY_WRITE))
    {
        iput(dir);
        return -EPERM;
    }
    if (!dir->i_op || !dir->i_op->unlink) {
		iput(dir);
		return -EPERM;
	}
	return dir->i_op->unlink(dir,basename,namelen);
}

// 建立符号链接。
// 为一个已存在文件创建一个符号链接（也称为软连接 - hard link）。
// 参数：oldname - 原路径名；newname - 新的路径名。
// 返回：若成功则返回0，否则返回出错号。
int sys_symlink(const char *oldname, const char *newname)
{
    struct inode *dir;
    const char *basename;
    int namelen;

    // 首先查找新路径名的最顶层目录的i节点dir，并返回最后的文件名及其长度。如果目录的i节点没有找到，则返回出错号。如果新路径名
    // 中不包括文件名，则放回新路径名目录的i节点，返回出错号。另外，如果用户没有在新目录中写的权限，则也不能建立连接，于是放回
    // 新路径名目录的i节点，返回出错号。
    dir = dir_namei(newname, &namelen, &basename, NULL);
    if (!dir)
        return -EACCES;
    if (!namelen)
    {
        iput(dir);
        return -EPERM;
    }
    if (!permission(dir, MAY_WRITE))
    {
        iput(dir);
        return -EACCES;
    }
    if (!dir->i_op || !dir->i_op->symlink) {
		iput(dir);
		return -EPERM;
	}
	return dir->i_op->symlink(dir,basename,namelen,oldname);
}

// 为文件建立一个文件名目录项。
// 为一个已存在的文件创建一个新链接（也称为硬连接 - hard link）。
// 参数：oldname - 原路径名；newname - 新的路径名。
// 返回：若成功则返回0,否则返回出错号。
int sys_link(const char *oldname, const char *newname)
{
    struct inode *oldinode, *dir;
    const char *basename;
    int namelen;

    // 首先对原文件名进行有效性验证，它应该存在并且不是一个目录名。所以我们先取原文件路径名对应的i节点oldinode。如果为0，则
    // 表示出错，返回出错号。如果原路径名对应的是一个目录名，则放回该i节点，也返回出错号。
    oldinode = namei(oldname);
    if (!oldinode)
        return -ENOENT;
    // 然后查找新路径名的最顶层目录的i节点dir，并返回最后的文件名及其长度。如果目录的i节点没有找到，则放回原路径名的i节点，返
    // 回出错号。如果新路径名中不包括文件名，则放回原路径名i节点和新路径名目录的i节点，返回出错号。
    dir = dir_namei(newname, &namelen, &basename, NULL);
    if (!dir)
    {
        iput(oldinode);
        return -EACCES;
    }
    if (!namelen)
    {
        iput(oldinode);
        iput(dir);
        return -EPERM;
    }
    // 我们不能跨设备建立硬链接。因此如果新路径名顶层目录的设备号与原路径名的设备号不一样，则放回新路径名目录的i节点和原路径名
    // 的i节点，返回出错号。另外，如果用户没有在新目录中写的权限，则也不能建立连接，于是放回新路径名目录的i节点和原路径名的i节点
    // 返回出错号。
    if (dir->i_dev != oldinode->i_dev)
    {
        iput(dir);
        iput(oldinode);
        return -EXDEV;
    }
    if (!permission(dir, MAY_WRITE))
    {
        iput(dir);
        iput(oldinode);
        return -EACCES;
    }
    if (!dir->i_op || !dir->i_op->link) {
		iput(dir);
		iput(oldinode);
		return -EPERM;
	}
	return dir->i_op->link(oldinode, dir, basename, namelen);
}
